function allocatePackage(thePackage) {
	var packages = thePackage.split(".");

	var currentPkg = window;
	for ( var i = 0; i < packages.length; i++) {
		var pkgName = packages[i];

		if (currentPkg[pkgName] == null) {
			currentPkg[pkgName] = {};
		}

		currentPkg = currentPkg[pkgName];
	}

	return currentPkg;
}

(function() {
	var testPkg = allocatePackage("it.koutifaris._tetris");

	/** *************** PieceType *********************** */
	var PieceType = {
		I : {
			toString : function() {
				return "I";
			}
		},
		J : {
			toString : function() {
				return "J";
			}
		},
		L : {
			toString : function() {
				return "L";
			}
		},
		O : {
			toString : function() {
				return "O";
			}
		},
		S : {
			toString : function() {
				return "S";
			}
		},
		T : {
			toString : function() {
				return "T";
			}
		},
		Z : {
			toString : function() {
				return "Z";
			}
		}
	};

	PieceType.valuesArray = null;
	PieceType.values = function() {
		if (PieceType.valuesArray == null) {
			var valuesArray = [];
			for (type in PieceType) {
				if (PieceType[type] != null && typeof PieceType[type] != 'function') {
					valuesArray.push(type);
				}
			}

			PieceType.valuesArray = valuesArray;
		}

		return PieceType.valuesArray;
	};

	testPkg.PieceType = PieceType;

	/** *************** Matrix *********************** */
	function Matrix(width, data) {
		this.width = width;
		if (data == null) {
			data = [];
		}
		this.data = data;
	}

	Matrix.prototype.getWidth = function() {
		return this.width;
	};

	Matrix.prototype.getHeight = function() {
		return this.i2y(this.data.length);
	};

	Matrix.prototype.get = function(row, col) {
		return this.data[this.xy2i(col, row)];
	};

	Matrix.prototype.set = function(row, col, value) {
		this.data[this.xy2i(col, row)] = value;
	};

	Matrix.prototype.xy2i = function(x, y) {
		return this.width * y + x;
	};

	Matrix.prototype.i2y = function(i) {
		return Math.floor(i / this.width);
	};

	Matrix.prototype.i2x = function(i) {
		return i % this.width;
	};

	Matrix.prototype.i2xy = function(i) {
		var y = this.i2y(i);
		var x = this.i2x(i);
		return {
			x : x,
			y : y
		};
	};

	Matrix.prototype.equals = function(other) {
		if (other == null) {
			return false;
		}

		if (this.width != other.width) {
			return false;
		}

		return arrayEquals(this.data, other.data);
	};

	function arrayEquals(a, a2) {
		if (a === a2) {
			return true;
		}

		if (a == null || a2 == null) {
			return false;
		}

		var length = a.length;
		if (a2.length != length) {
			return false;
		}

		for ( var i = 0; i < length; i++) {
			if (a[i] != a2[i])
				return false;
		}

		return true;
	}

	Matrix.prototype.clone = function() {
		return new Matrix(this.width, this.data.slice(0));
	};

	Matrix.prototype.transpose = function() {
		var toCol = this.getWidth();
		var toRow = this.getHeight();
		var newMatrix = new Matrix(this.getHeight());

		for ( var c = 0; c < toCol; c++) {
			for ( var r = 0; r < toRow; r++) {
				newMatrix.set(c, r, this.get(r, c));
			}
		}

		this.width = newMatrix.getWidth();
		this.data = newMatrix.data;

		return this;
	};

	Matrix.prototype.swap = function(i, j) {
		var tmp = this.data[i];
		this.data[i] = this.data[j];
		this.data[j] = tmp;

		return this;
	};

	Matrix.prototype.swapColumns = function() {
		var toCol = Math.floor(this.getWidth() / 2);
		var toRow = this.getHeight();
		var width = this.getWidth();

		for ( var c = 0; c < toCol; c++) {
			for ( var r = 0; r < toRow; r++) {
				var x = c;
				var y = r;

				var i = this.xy2i(x, y);
				var j = this.xy2i(width - x - 1, y);

				this.swap(i, j);
			}
		}

		return this;
	};

	Matrix.prototype.swapRows = function() {
		var toCol = this.getWidth();
		var toRow = Math.floor(this.getHeight() / 2);
		var height = this.getHeight();

		for ( var c = 0; c < toCol; c++) {
			for ( var r = 0; r < toRow; r++) {
				var x = c;
				var y = r;

				var i = this.xy2i(x, y);
				var j = this.xy2i(x, height - y - 1);

				this.swap(i, j);
			}
		}

		return this;
	};

	Matrix.prototype.rotate = function(direction) {
		var numRotations = Math.abs(direction) % 4;

		for ( var i = 0; i < numRotations; i++) {
			if (direction > 0) {
				this.transpose();
				this.swapColumns();
			} else if (direction < 0) {
				this.transpose();
				this.swapRows();
			}
		}

		return this;
	};

	testPkg.Matrix = Matrix;

	/** *************** PieceMatrixIterator *********************** */
	function PieceMatrixIterator(pieceMatrix) {
		this.pieceMatrix = pieceMatrix;
		this.r = 0;
		this.c = -1;
		this.index = 0;
	}

	PieceMatrixIterator.prototype.hasNext = function() {
		return this.index < 4;
	};

	PieceMatrixIterator.prototype.next = function() {
		var result = null;

		while (this.c < this.pieceMatrix.getWidth() && this.r < this.pieceMatrix.getHeight()) {
			this.c += 1;
			if (this.c >= this.pieceMatrix.getWidth()) {
				this.c = 0;
				this.r++;
			}

			var value = this.pieceMatrix.get(this.r, this.c);
			if (value != 0) {
				result = {
					c : this.c,
					r : this.r
				};
				this.index++;
				break;
			}

		}

		return result;
	};

	testPkg.PieceMatrixIterator = PieceMatrixIterator;

	/** *************** TetrisPiece *********************** */
	function TetrisPiece(pieceType, rotationSystem) {
		this.rotationSystem = rotationSystem;
		this.pieceType = pieceType;
		this.r = 0;
		this.c = 0;
		this.currentRotation = 0;
		this.updateCurrentMatrix();
	}

	TetrisPiece.prototype.getCurrentMatrix = function() {
		return this.currentPieceMatrix;
	};

	TetrisPiece.prototype.updateCurrentMatrix = function() {
		this.currentPieceMatrix = this.rotationSystem.getPieceMatrix(this.pieceType, this.currentRotation);
	};

	TetrisPiece.prototype.setPosition = function(r, c) {
		this.r = r;
		this.c = c;
	};

	TetrisPiece.prototype.squaresIterator = function() {
		var iterator = new PieceMatrixIterator(this.currentPieceMatrix);
		var matrixHeight = this.currentPieceMatrix.getHeight();
		var rOffset = this.r;
		var cOffset = this.c;
		var boardCoordinatesIterator = {
			hasNext : function() {
				return iterator.hasNext();
			},
			next : function() {
				var coords = iterator.next();
				var newCoords = {};
				newCoords.r = matrixHeight - coords.r - 1 + rOffset;
				newCoords.c = coords.c + cOffset;

				return newCoords;
			}
		};

		return boardCoordinatesIterator;
	};

	TetrisPiece.prototype.rotate = function(direction) {
		this.currentRotation += (direction >= 0) ? ((direction == 0) ? 0 : 1) : -1;
		this.updateCurrentMatrix();
	};

	testPkg.TetrisPiece = TetrisPiece;

	/** *************** TetrisBoard *********************** */
	function TetrisBoard(width) {
		this.width = width;
		this.piecesStack = new Matrix(width);
	}

	TetrisBoard.prototype.placeElement = function(piece) {
		var pieceIterator = piece.squaresIterator();
		while (pieceIterator.hasNext()) {
			var coords = pieceIterator.next();
			this.piecesStack.set(coords.r, coords.c, piece);
		}
	};

	TetrisBoard.prototype.hitsStack = function(piece) {
		var pieceIterator = piece.squaresIterator();
		var height = this.piecesStack.getHeight();

		while (pieceIterator.hasNext()) {
			var coords = pieceIterator.next();
			if (coords.r <= height) {

				if (this.piecesStack.get(coords.r, coords.c) != null) {
					return true;
				}
			}
		}

		return false;
	};

	testPkg.TetrisBoard = TetrisBoard;

	/** *************** initHelper *********************** */
	var initHelper = {};
	initHelper.initKeyboard = function(element, keyboardHandler) {
		if (element.getAttribute('tabIndex') == null) {
			element.setAttribute('tabIndex', 0);
		}

		if (keyboardHandler == null) {
			keyboardHandler = {};
		}

		var keyHandlerMockup = function() {
			console.log(event.type);
		};

		if (keyboardHandler.onKeyDown == null) {
			keyboardHandler.onKeyDown = keyHandlerMockup;
		}

		if (keyboardHandler.onKeyPress == null) {
			keyboardHandler.onKeyPress = keyHandlerMockup;
		}

		if (keyboardHandler.onKeyUp == null) {
			keyboardHandler.onKeyUp = keyHandlerMockup;
		}

		element.onkeydown = function(event) {
			keyboardHandler.onKeyDown(event);
		};

		element.onkeypress = function(event) {
			keyboardHandler.onKeyPress(event);
		};

		element.onkeyup = function(event) {
			keyboardHandler.onKeyUp(event);
		};
	};

	/** *************** Tetris *********************** */
	function Tetris(canvas, width, height) {
		if (!(this instanceof Tetris)) {
			return new Tetris(canvas, width, height);
		}

		this.canvas = canvas;

		if (width == null) {
			width = 10;
		}

		if (height == null) {
			height = 30;
		}

		this.width = width;
		this.height = height;

		initHelper.initKeyboard(canvas);

		return this;
	}

	Tetris.prototype.initKeyboard = function() {

	};

	Tetris.prototype.start = function() {
	};

	var tetrisPkg = allocatePackage("it.koutifaris.tetris");
	tetrisPkg.Tetris = Tetris;

}());